/*
 * ExpressionParser.cpp
 *
 *  Created on: 6 Mar 2020
 *      Author: David
 */

#include "ExpressionParser.h"

#include "GCodeBuffer.h"
#include <Platform/RepRap.h>
#include <Platform/Platform.h>
#include <General/NamedEnum.h>
#include <General/NumericConverter.h>
#include <Hardware/ExceptionHandlers.h>
#include <PrintMonitor/PrintMonitor.h>

#include <climits>
#include <limits>

#ifdef exists
# undef exists
#endif

namespace StackUsage
{
	// The following values are the number of bytes of stack space needed by the corresponding functions and functions they call,
	// not counting other called functions that call CheckStack. They are obtained from file ExpressionParser.su generated by the compiler.
#if SAME70
	constexpr uint32_t ParseInternal = 80;
	constexpr uint32_t ParseIdentifierExpression = 288;
	constexpr uint32_t ParseGeneralArray = 360;
	constexpr uint32_t ReadArrayFromFile = 384;
	constexpr uint32_t ApplyObjectModelArrayIndex = 112;
	constexpr uint32_t GetObjectValueUsingTableNumber = 48;
#else
	constexpr uint32_t ParseInternal = 72;
	constexpr uint32_t ParseIdentifierExpression = 288;
	constexpr uint32_t ParseGeneralArray = 352;
	constexpr uint32_t ReadArrayFromFile = 376;
	constexpr uint32_t ApplyObjectModelArrayIndex = 120;
	constexpr uint32_t GetObjectValueUsingTableNumber = 56;
#endif
}

// Read a character from the text file.
// If we reach end of line or end of file, set currentCharacter to 0 and set fileFinished to true.
// Else increment the character count and set currentCharacter to the character we read.
void LineReader::ReadChar() noexcept
{
	if (!fileFinished)
	{
		fileFinished = (!f->Read(currentCharacter) || currentCharacter == '\r' || currentCharacter == '\n');
	}
	if (fileFinished)
	{
		currentCharacter = 0;
	}
	else
	{
		++charsRead;
	}
}

void LineReader::SkipTabsAndSpaces() noexcept
{
	while (currentCharacter == ' ' || currentCharacter == '\t')
	{
		ReadChar();
	}
}

// These can't be declared locally inside ParseIdentifierExpression because NamedEnum includes static data
NamedEnum(NamedConstant, unsigned int, _false, iterations, line, _null, pi, _result, _true, input);
NamedEnum(Function, unsigned int, abs, acos, asin, atan, atan2, ceil, cos, datetime, degrees, drop, exists, exp, fileexists, fileread, find, floor, isnan, log, max, min, mod, pow, radians, random, round, sin, sqrt, square, take, tan, vector);

const char *_ecv_array const InvalidExistsMessage = "invalid 'exists' expression";
const char *_ecv_array const ExpectedNonNegativeIntMessage = "expected non-negative integer";

ExpressionParser::ExpressionParser(const GCodeBuffer *_ecv_null p_gb, const char *_ecv_array text, const char *_ecv_array textLimit, int p_column) noexcept
	: currentp(text), startp(text), endp(textLimit), gb(p_gb), column(p_column)
{
}

// Evaluate a bracketed expression
void ExpressionParser::ParseExpectKet(ExpressionValue& rslt, bool evaluate, char closingBracket) THROWS(GCodeException)
{
	CheckStack(StackUsage::ParseInternal);
	ParseInternal(rslt, evaluate, 0);
	if (CurrentCharacter() == closingBracket)
	{
		AdvancePointer();
	}
	else if (CurrentCharacter() == ',' && closingBracket == '}')
	{
		CheckStack(StackUsage::ParseGeneralArray);
		ParseGeneralArray(rslt, evaluate);
	}
	else
	{
		ThrowParseException("expected '%c'", (uint32_t)closingBracket);
	}
}

// Handle an index after an object model array expression.
void ExpressionParser::ApplyObjectModelArrayIndex(ExpressionValue& rslt, int indexCol, uint32_t indexValue, bool evaluate) THROWS(GCodeException)
{
	const ObjectModelArrayTableEntry *_ecv_null const entry = rslt.omVal->FindObjectModelArrayEntry(rslt.param & 0xFF);
	if (entry == nullptr)
	{
		THROW_INTERNAL_ERROR;
	}
	ObjectExplorationContext context;
	context.AddIndex(rslt.param >> 8);
	ReadLocker lock(entry->lockPointer);
	const size_t numElements = entry->GetNumElements(rslt.omVal, context);
	if (indexValue < numElements)
	{
		context.AddIndex(indexValue);
		rslt = entry->GetElement(rslt.omVal, context);
	}
	else if (evaluate)
	{
		throw GCodeException(gb, indexCol, ArrayIndexOutOfRangeText);
	}
	else
	{
		rslt.SetNull(nullptr);
	}
}

// Evaluate an expression. Do not call this one recursively!
ExpressionValue ExpressionParser::Parse(bool evaluate) THROWS(GCodeException)
{
	obsoleteField.Clear();
	ExpressionValue result;
	ParseInternal(result, evaluate, 0);
	if (!obsoleteField.IsEmpty())
	{
		reprap.GetPlatform().MessageF(WarningMessage, "obsolete object model field %s queried\n", obsoleteField.c_str());
	}
	return result;
}

// Evaluate an expression internally, stopping before any binary operators with priority 'priority' or lower
// This is recursive, so avoid allocating large amounts of data on the stack
void ExpressionParser::ParseInternal(ExpressionValue& val, bool evaluate, uint8_t priority) THROWS(GCodeException)
{
	// Lists of binary operators and their priorities
	static constexpr const char *_ecv_array operators = "?^&|!=<>+-*/";				// for multi-character operators <= and >= and != this is the first character
	static constexpr uint8_t priorities[] = { 1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6 };
	constexpr uint8_t UnaryPriority = 10;									// must be higher than any binary operator priority
	static_assert(ARRAY_SIZE(priorities) == strlen(operators));

	// Start by looking for a unary operator or opening bracket
	const char c = SkipWhiteSpace();
	switch (c)
	{
	case '"':
		ParseQuotedString(val);
		break;

	case '\'':
		ParseCharacter(val);
		break;

	case '-':
		AdvancePointer();
		CheckStack(StackUsage::ParseInternal);
		ParseInternal(val, evaluate, UnaryPriority);
		switch (val.GetType())
		{
		case TypeCode::Int32:
			val.iVal = -val.iVal;		//TODO overflow check
			break;

		case TypeCode::Float:
			val.fVal = -val.fVal;
			break;

		default:
			ThrowParseException("expected numeric value after '-'");
		}
		break;

	case '+':
		AdvancePointer();
		CheckStack(StackUsage::ParseInternal);
		ParseInternal(val, evaluate, UnaryPriority);
		switch (val.GetType())
		{
		case TypeCode::Uint32:
			// Convert enumeration to integer
			val.SetInt((int32_t)val.uVal);
			break;

		case TypeCode::Int32:
		case TypeCode::Float:
			break;

		case TypeCode::DateTime_tc:					// unary + converts a DateTime to a seconds count
			val.SetInt((uint32_t)val.Get56BitValue());
			break;

		default:
			ThrowParseException("expected numeric or enumeration value after '+'");
		}
		break;

	case '#':
		AdvancePointer();
		if (isAlpha(SkipWhiteSpace()))
		{
			// Probably applying # to an object model array, so optimise by asking the OM for just the length
			CheckStack(StackUsage::ParseIdentifierExpression);
			ParseIdentifierExpression(val, evaluate, true, false);
		}
		else
		{
			CheckStack(StackUsage::ParseInternal);
			ParseInternal(val, evaluate, UnaryPriority);
			ApplyLengthOperator(val, evaluate);
		}
		break;

	case '{':
		AdvancePointer();
		ParseExpectKet(val, evaluate, '}');
		break;

	case '(':
		AdvancePointer();
		ParseExpectKet(val, evaluate, ')');
		break;

	case '!':
		AdvancePointer();
		CheckStack(StackUsage::ParseInternal);
		ParseInternal(val, evaluate, UnaryPriority);
		ConvertToBool(val, evaluate);
		val.bVal = !val.bVal;
		break;

	default:
		if (isDigit(c))						// looks like a number
		{
			ParseNumber(val);
		}
		else if (isAlpha(c))				// looks like a variable name
		{
			CheckStack(StackUsage::ParseIdentifierExpression);
			ParseIdentifierExpression(val, evaluate, false, false);
		}
		else
		{
			ThrowParseException("expected an expression");
		}
		break;
	}

	// Check for trailing index expressions
	for (;;)
	{
		if (SkipWhiteSpace() != '[')
		{
			break;
		}
		const int indexCol = GetColumn();
		AdvancePointer();
		const uint32_t indexValue = ParseUnsigned();
		if (CurrentCharacter() != ']')
		{
			ThrowParseException("expected ']'");
		}
		AdvancePointer();
		switch (val.GetType())
		{
		case TypeCode::ObjectModelArray:
			CheckStack(StackUsage::ApplyObjectModelArrayIndex);
			ApplyObjectModelArrayIndex(val, indexCol, indexValue, evaluate);			// call out to separate function to reduce stack usage of this one from 128 to 32 bytes
			break;

		case TypeCode::HeapArray:
			{
				ReadLocker lock(Heap::heapLock);				// must have a read lock on heapLock when calling GetNumElements or GetElement
				if (!val.ahVal.GetElement(indexValue, val))		// if index was out of bounds
				{
					if (evaluate)
					{
						throw GCodeException(gb, indexCol, ArrayIndexOutOfRangeText);
					}
					else
					{
						val.SetNull(nullptr);
					}
				}
			}
			break;

		case TypeCode::CString:
			{
				const size_t len = strlen(val.sVal);
				if (indexValue >= len)
				{
					throw GCodeException(gb, indexCol, ArrayIndexOutOfRangeText);
				}
				val.SetChar(val.sVal[indexValue]);
			}
			break;

		case TypeCode::HeapString:
			{
				ReadLockedPointer<const char> p = val.shVal.Get();
				if (p.IsNull() || indexValue >= strlen(p.Ptr()))
				{
					throw GCodeException(gb, indexCol, ArrayIndexOutOfRangeText);
				}
				val.SetChar(p.Ptr()[indexValue]);
			}
			break;

		default:
			if (evaluate)
			{
				throw GCodeException(gb, indexCol, "left operand of [ ] is not an array or string");
			}
			val.SetNull(nullptr);
			break;
		}
	}

	// See if it is followed by a binary operator
	do
	{
		char opChar = SkipWhiteSpace();
		if (opChar == 0)	// don't pass null to strchr
		{
			return;
		}

		const char *_ecv_array _ecv_null const q = strchr(operators, opChar);
		if (q == nullptr)
		{
			return;
		}
		const size_t index = q - operators;
		const uint8_t opPrio = priorities[index];
		if (opPrio <= priority)
		{
			return;
		}

		AdvancePointer();								// skip the [first] operator character

		// Handle >= and <= and !=
		bool invert = false;
		if (opChar == '!')
		{
			if (CurrentCharacter() != '=')
			{
				ThrowParseException("expected '='");
			}
			invert = true;
			AdvancePointer();
			opChar = '=';
		}
		else if ((opChar == '>' || opChar == '<') && CurrentCharacter() == '=')
		{
			invert = true;
			AdvancePointer();
			opChar ^= ('>' ^ '<');			// change < to > or vice versa
		}

		// Allow == && || as alternatives to = & |
		if ((opChar == '=' || opChar == '&' || opChar == '|') && CurrentCharacter() == opChar)
		{
			AdvancePointer();
		}

		// Handle operators that do not always evaluate their second operand
		switch (opChar)
		{
		case '&':
			ConvertToBool(val, evaluate);
			{
				ExpressionValue val2;
				CheckStack(StackUsage::ParseInternal);
				ParseInternal(val2, evaluate && val.bVal, opPrio);		// get the next operand
				if (val.bVal)
				{
					ConvertToBool(val2, evaluate);
					val.bVal = val2.bVal;
				}
			}
			break;

		case '|':
			ConvertToBool(val, evaluate);
			{
				ExpressionValue val2;
				CheckStack(StackUsage::ParseInternal);
				ParseInternal(val2, evaluate && !val.bVal, opPrio);		// get the next operand
				if (!val.bVal)
				{
					ConvertToBool(val2, evaluate);
					val.bVal = val2.bVal;
				}
			}
			break;

		case '?':
			ConvertToBool(val, evaluate);
			{
				const bool b = val.bVal;
				ExpressionValue val2;
				CheckStack(StackUsage::ParseInternal);
				ParseInternal(((b) ? val : val2), evaluate && b, opPrio);		// get the second operand
				if (CurrentCharacter() != ':')
				{
					ThrowParseException("expected ':'");
				}
				AdvancePointer();
				// We recently checked the stack for a call to ParseInternal, no need to do it again
				ParseInternal(((b) ? val2 : val), evaluate && !b, opPrio - 1);	// get the third operand, which may be a further conditional expression
				return;
			}

		default:
			// Handle binary operators that always evaluate both operands
			{
				ExpressionValue val2;
				CheckStack(StackUsage::ParseInternal);
				ParseInternal(val2, evaluate, opPrio);	// get the next operand
				switch(opChar)
				{
				case '+':
					if (val.GetType() == TypeCode::DateTime_tc)
					{
						if (val2.GetType() == TypeCode::Uint32)
						{
							val.Set56BitValue(val.Get56BitValue() + val2.uVal);
						}
						else if (val2.GetType() == TypeCode::Int32)
						{
							val.Set56BitValue((int64_t)val.Get56BitValue() + val2.iVal);
						}
						else if (evaluate)
						{
							ThrowParseException("invalid operand types");
						}
					}
					else
					{
						BalanceNumericTypes(val, val2, evaluate);
						if (val.GetType() == TypeCode::Float)
						{
							val.fVal += val2.fVal;
							val.param = max(val.param, val2.param);
						}
						else
						{
							val.iVal += val2.iVal;
						}
					}
					break;

				case '-':
					if (val.GetType() == TypeCode::DateTime_tc)
					{
						if (val2.GetType() == TypeCode::DateTime_tc)
						{
							// Difference of two data/times
							val.SetInt((int32_t)(val.Get56BitValue() - val2.Get56BitValue()));
						}
						else if (val2.GetType() == TypeCode::Uint32)
						{
							val.Set56BitValue(val.Get56BitValue() - val2.uVal);
						}
						else if (val2.GetType() == TypeCode::Int32)
						{
							val.Set56BitValue((int64_t)val.Get56BitValue() - val2.iVal);
						}
						else if (evaluate)
						{
							ThrowParseException("invalid operand types");
						}
					}
					else
					{
						BalanceNumericTypes(val, val2, evaluate);
						if (val.GetType() == TypeCode::Float)
						{
							val.fVal -= val2.fVal;
							val.param = max(val.param, val2.param);
						}
						else
						{
							val.iVal -= val2.iVal;
						}
					}
					break;

				case '*':
					BalanceNumericTypes(val, val2, evaluate);
					if (val.GetType() == TypeCode::Float)
					{
						val.fVal *= val2.fVal;
						val.param = max(val.param, val2.param);
					}
					else
					{
						val.iVal *= val2.iVal;
					}
					break;

				case '/':
					ConvertToFloat(val, evaluate);
					ConvertToFloat(val2, evaluate);
					val.fVal /= val2.fVal;
					val.param = MaxFloatDigitsDisplayedAfterPoint;
					break;

				case '>':
					BalanceTypes(val, val2, evaluate);
					{
						bool bResult;
						switch (val.GetType())
						{
						case TypeCode::Int32:
							bResult = (val.iVal > val2.iVal);
							break;

						case TypeCode::Float:
							bResult = (val.fVal > val2.fVal);
							break;

						case TypeCode::DateTime_tc:
							bResult = val.Get56BitValue() > val2.Get56BitValue();
							break;

						case TypeCode::Bool:
							bResult = (val.bVal && !val2.bVal);
							break;

						default:
							if (evaluate)
							{
								ThrowParseException("expected numeric or Boolean operands to comparison operator");
							}
							bResult = false;
							break;
						}
						val.SetBool((invert) ? !bResult : bResult);
					}
					break;

				case '<':
					BalanceTypes(val, val2, evaluate);
					{
						bool bResult;
						switch (val.GetType())
						{
						case TypeCode::Int32:
							bResult = (val.iVal < val2.iVal);
							break;

						case TypeCode::Float:
							bResult = (val.fVal < val2.fVal);
							break;

						case TypeCode::DateTime_tc:
							bResult = val.Get56BitValue() < val2.Get56BitValue();
							break;

						case TypeCode::Bool:
							bResult = (!val.bVal && val2.bVal);
							break;

						default:
							if (evaluate)
							{
								ThrowParseException("expected numeric or Boolean operands to comparison operator");
							}
							bResult = false;
							break;
						}
						val.SetBool((invert) ? !bResult : bResult);
					}
					break;

				case '=':
					{
						bool bResult;
						// Before balancing, handle comparisons with null
						if (val.GetType() == TypeCode::None)
						{
							bResult = (val2.GetType() == TypeCode::None);
						}
						else if (val2.GetType() == TypeCode::None)
						{
							bResult = false;
						}
						else
						{
							BalanceTypes(val, val2, evaluate);
							switch (val.GetType())
							{
							case TypeCode::ObjectModel_tc:
								ThrowParseException("cannot compare objects");

							case TypeCode::Int32:
								bResult = (val.iVal == val2.iVal);
								break;

							case TypeCode::Uint32:
								bResult = (val.uVal == val2.uVal);
								break;

							case TypeCode::Float:
								bResult = (val.fVal == val2.fVal);
								break;

							case TypeCode::DateTime_tc:
								bResult = val.Get56BitValue() == val2.Get56BitValue();
								break;

							case TypeCode::Bool:
								bResult = (val.bVal == val2.bVal);
								break;

							case TypeCode::CString:
								bResult = (strcmp(val.sVal, (val2.GetType() == TypeCode::HeapString) ? val2.shVal.Get().Ptr() : val2.sVal) == 0);
								break;

							case TypeCode::HeapString:
								bResult = (strcmp(val.shVal.Get().Ptr(), (val2.GetType() == TypeCode::HeapString) ? val2.shVal.Get().Ptr() : val2.sVal) == 0);
								break;

							default:
								if (evaluate)
								{
									ThrowParseException("unexpected operand type to equality operator");
								}
								bResult = false;
								break;
							}
						}
						val.SetBool((invert) ? !bResult : bResult);
					}
					break;

				case '^':
					StringConcat(val, val2);
					break;
				}
			}
		}
	} while (true);
}

// Concatenate val1 and val2 and assign the result to val1
// This is written as a separate function because it needs a temporary string buffer, and its caller is recursive. Its declaration must be declared 'noinline'.
/*static*/ void  ExpressionParser::StringConcat(ExpressionValue &val, ExpressionValue &val2) noexcept
{
    String<MaxStringExpressionLength> str;
    val.AppendAsString(str.GetRef());
    val2.AppendAsString(str.GetRef());
    StringHandle sh(str.c_str());
    val.SetStringHandle(sh);
}

bool ExpressionParser::ParseBoolean() THROWS(GCodeException)
{
	ExpressionValue val = Parse();
	ConvertToBool(val, true);
	return val.bVal;
}

float ExpressionParser::ParseFloat() THROWS(GCodeException)
{
	ExpressionValue val = Parse();
	ConvertToFloat(val, true);
	return val.fVal;
}

int32_t ExpressionParser::ParseInteger() THROWS(GCodeException)
{
	ExpressionValue val = Parse();
	ConvertToInteger(val, true);
	return val.iVal;
}

uint32_t ExpressionParser::ParseUnsigned() THROWS(GCodeException)
{
	ExpressionValue val = Parse();
	ConvertToUnsigned(val, true);
	return val.uVal;
}

DriverId ExpressionParser::ParseDriverId() THROWS(GCodeException)
{
	ExpressionValue val = Parse();
	ConvertToDriverId(val, true);
	return val.GetDriverIdValue();
}

void ExpressionParser::ParseArray(size_t& length, function_ref<void(ExpressionValue& ev, size_t index) THROWS(GCodeException)> processElement) THROWS(GCodeException)
{
	size_t numElements = 0;
	AdvancePointer();									// skip the '{'
	ExpressionValue ev = Parse(true);					// parse the first element
	if (CurrentCharacter() == EXPRESSION_LIST_SEPARATOR)
	{
		// We have an explicit list of elements, so each one must convert to the required type
		for (;;)
		{
			processElement(ev, numElements);
			++numElements;
			if (CurrentCharacter() != EXPRESSION_LIST_SEPARATOR) break;
			AdvancePointer();
			if (SkipWhiteSpace() == '}') break;		// we allow a trailing command in an array
			if (numElements == length)
			{
				ThrowParseException("Array too long");
			}
			ev = Parse(true);
		}
	}
	else
	{
		// We have a single expression. If it is an array, convert the elements; else convert it into a sigle-expresison array.
		switch (ev.GetType())
		{
		case TypeCode::HeapArray:
			{
				ReadLocker locker(Heap::heapLock);
				const size_t len = ev.ahVal.GetNumElements();
				if (len == 0)
				{
					ThrowParseException("expected a non-empty array");
				}
				if (len > length)
				{
					ThrowParseException("array too long");
				}
				while (numElements < len)
				{
					ExpressionValue ev2;
					(void)ev.ahVal.GetElement(numElements, ev2);
					processElement(ev2, numElements);
					++numElements;
				}
			}
			break;

		case TypeCode::ObjectModelArray:
			{
				const ObjectModelArrayTableEntry *const entry = _ecv_not_null(ev.omVal->FindObjectModelArrayEntry(ev.param & 0xFF));
				ObjectExplorationContext context;
				ReadLocker locker(entry->lockPointer);
				const size_t len = entry->GetNumElements(ev.omVal, context);
				if (len == 0)
				{
					ThrowParseException("expected a non-empty array");
				}
				if (len > length)
				{
					ThrowParseException("array too long");
				}
				while (numElements < len)
				{
					context.AddIndex(numElements);
					ExpressionValue ev2 = entry->GetElement(ev.omVal, context);
					context.RemoveIndex();
					processElement(ev2, numElements);
					++numElements;
				}
			}
			break;

		default:
			processElement(ev, numElements);
			++numElements;
		}
	}

	if (CurrentCharacter() != '}')
	{
		ThrowParseException("Expected '}'");
	}
	AdvancePointer();					// skip the '}'
	length = numElements;
}

// This is called when we expect a non-empty float array parameter and we have encountered (but not skipped) '{'
void ExpressionParser::ParseFloatArray(float arr[], size_t& length) THROWS(GCodeException)
{
	ParseArray(length, [this, &arr](ExpressionValue& ev, size_t index) { ConvertToFloat(ev, true); arr[index] = ev.fVal; });
}

void ExpressionParser::ParseIntArray(int32_t arr[], size_t& length) THROWS(GCodeException)
{
	ParseArray(length, [this, &arr](ExpressionValue& ev, size_t index) { ConvertToInteger(ev, true); arr[index] = ev.iVal; });
}

void ExpressionParser::ParseUnsignedArray(uint32_t arr[], size_t& length) THROWS(GCodeException)
{
	ParseArray(length, [this, &arr](ExpressionValue& ev, size_t index) { ConvertToUnsigned(ev, true); arr[index] = ev.uVal; });
}

void ExpressionParser::ParseDriverIdArray(DriverId arr[], size_t& length) THROWS(GCodeException)
{
	ParseArray(length, [this, &arr](ExpressionValue& ev, size_t index) { ConvertToDriverId(ev, true); arr[index] = ev.GetDriverIdValue(); });
}

// Parse the rest of an array. We have already parsed the first element and found but not skipped a comma. The array should be terminated with '}'.
void ExpressionParser::ParseGeneralArray(ExpressionValue& firstElementAndResult, bool evaluate) THROWS(GCodeException)
{
	// Parse the array elements into a temporary array
	ExpressionValue elements[MaxLiteralArrayElements];
	elements[0] = std::move(firstElementAndResult);
	size_t index = 1;
	do
	{
		if (index == MaxLiteralArrayElements)
		{
			ThrowParseException("too many array elements");
		}
		AdvancePointer();					// skip the comma
		if (SkipWhiteSpace() == '}')
		{
			break;							// we allow a trailing comma and it can be used to distinguish a 1-element array from a bracketed value
		}
		ParseInternal(elements[index], evaluate, 0);
		++index;
	} while (CurrentCharacter() == ',');

	if (CurrentCharacter() != '}')
	{
		ThrowParseException("expected '}'");
	}
	AdvancePointer();

	// Copy the temporary array to the heap
	ArrayHandle ah;
	{
		WriteLocker locker(Heap::heapLock);						// prevent other tasks modifying the heap
		ah.Allocate(index);
		for (size_t i = 0; i < index; ++i)
		{
			ah.AssignElement(i, elements[i]);
		}
	}
	firstElementAndResult.SetArrayHandle(ah);
}

void ExpressionParser::BalanceNumericTypes(ExpressionValue& val1, ExpressionValue& val2, bool evaluate) const THROWS(GCodeException)
{
	// First convert any Uint64 or Uint32 operands to float
	if (val1.GetType() == TypeCode::Uint64 || val1.GetType() == TypeCode::Uint32)
	{
		ConvertToFloat(val1, evaluate);
	}
	if (val2.GetType() == TypeCode::Uint64 || val2.GetType() == TypeCode::Uint32)
	{
		ConvertToFloat(val2, evaluate);
	}

	if (val1.GetType() == TypeCode::Float)
	{
		ConvertToFloat(val2, evaluate);						// both are now float
	}
	else if (val2.GetType() == TypeCode::Float)
	{
		ConvertToFloat(val1, evaluate);						// both are now float
	}
	else if (val1.GetType() != TypeCode::Int32 || val2.GetType() != TypeCode::Int32)
	{
		if (evaluate)
		{
			ThrowParseException("expected numeric operands");
		}
		val1.SetInt(0);
		val2.SetInt(0);
	}
}

// Balance types v2 and v2 and store the min or max of them in v1
void ExpressionParser::EvaluateMinOrMax(ExpressionValue& v1, ExpressionValue& v2, bool evaluate, bool isMax) const THROWS(GCodeException)
{
	BalanceNumericTypes(v1, v2, evaluate);
	if (v1.GetType() == TypeCode::Float)
	{
		v1.fVal = ((isMax) ? max<float> : min<float>)(v1.fVal, v2.fVal);
		v1.param = max(v2.param, v2.param);
	}
	else
	{
		v1.iVal = (isMax ? max<int32_t> : min<int32_t>)(v1.iVal, v2.iVal);
	}
}

// Open a text file and read array elements from it.
// On entry, 'rslt' holds the filename and has type CString or HeapString. On return it holds the result array.
// Only the first line of the file is read. Possible future extension: allow escape characters in character literals, then allow '\n' as a delimiter.
void ExpressionParser::ReadArrayFromFile(ExpressionValue& rslt, unsigned int offset, unsigned int length, char delimiter) const THROWS(GCodeException)
{
	if (length > MaxFileReadArrayElements)
	{
		ThrowParseException("fileRead function: too many elements requested");
	}

	FileStore *_ecv_null f;
	{
		String<MaxFilenameLength> fname;
		{
			// The following locks the heap if rslt was a HeapString, therefore we must release it and stop using it before we create the result array
			ReadLockedPointer<const char> stringParam = (rslt.GetType() == TypeCode::HeapString) ? rslt.shVal.Get() : ReadLockedPointer<const char>(nullptr, rslt.sVal);
			MassStorage::CombineName(fname.GetRef(), "0:/", stringParam.Ptr());
		}
		f = MassStorage::OpenFile(fname.c_str(), OpenMode::read, 0);
		if (f == nullptr)
		{
			ThrowParseException("fileRead function: failed to open file");
		}
	}

	LineReader reader(f);
	ExpressionValue elements[MaxFileReadArrayElements];
	size_t elemIndex = 0;
	while (elemIndex < length && !reader.FileFinished())
	{
		// Parse an element to potentially add to the array
		try
		{
			ReadArrayElementFromFile(elements[elemIndex], reader, delimiter);
		}
		catch (...)
		{
			reader.Close();
			throw;
		}
		if (offset != 0)
		{
			--offset;					// skip adding this element
		}
		else
		{
			++elemIndex;
		}
	}

	reader.Close();

	// Move the array to the heap
	ArrayHandle ah;
	{
		WriteLocker locker(Heap::heapLock);						// prevent other tasks modifying the heap
		ah.Allocate(elemIndex);
		for (size_t i = 0; i < elemIndex; ++i)
		{
			ah.AssignElement(i, elements[i]);
		}
	}
	rslt.SetArrayHandle(ah);
}

// Read an element from the file.
// On entry, startColumn is the number of characters we have read so far.
// On return, startColumn has been updated. Returns true if we reached the end of the file or end of the line, else false and the delimiter has been read.
void ExpressionParser::ReadArrayElementFromFile(ExpressionValue& rslt, LineReader& reader, char delimiter) const THROWS(GCodeException)
{
	reader.ReadChar();
	reader.SkipTabsAndSpaces();
	if (reader.FileFinished() || reader.CurrentCharacter() == delimiter)
	{
		rslt.SetNull(nullptr);
	}
	else
	{
		bool err = false;
		if (reader.CurrentCharacter() == '\'')
		{
			// Parse a character literal
			reader.ReadChar();
			if (reader.FileFinished())
			{
				err = true;
			}
			else
			{
				const char resultChar = reader.CurrentCharacter();
				reader.ReadChar();
				if (reader.CurrentCharacter() != '\'')
				{
					err = true;
				}
				else
				{
					rslt.SetChar(resultChar);
					reader.ReadChar();
				}
			}
		}
		else if (reader.CurrentCharacter() == '"')
		{
			// Parse a string literal, allowing for double quote inside it
			String<StringLength100> element;
			for (;;)
			{
				reader.ReadChar();
				if (reader.FileFinished())
				{
					err = true;
					break;
				}
				if (reader.CurrentCharacter() == '"')
				{
					reader.ReadChar();
					if (reader.CurrentCharacter() != '"')
					{
						break;
					}
				}
				element.cat(reader.CurrentCharacter());
			}
			if (!err)
			{
				rslt.SetStringHandle(StringHandle(element.c_str()));
			}
		}
		else if (reader.CurrentCharacter() == '+' || reader.CurrentCharacter() == '-' || isDigit(reader.CurrentCharacter()))
		{
			NumericConverter conv;
			err = !conv.Accumulate(reader.CurrentCharacter(), NumericConverter::AcceptSignedFloat | NumericConverter::AcceptHex,
										[&reader, delimiter]()->char
										{
											reader.ReadChar();
											const char c = reader.CurrentCharacter();
											return (c == delimiter) ? 0 : c;				// allow '.' to be used as the delimiter
										}
								  );
			if (!err)
			{
				if (conv.FitsInInt32())
				{
					rslt.SetInt(conv.GetInt32());
				}
				else
				{
					rslt.SetFloat(conv.GetFloat());
				}
			}
		}
		else
		{
			err = true;
		}

		// Skip any trailing spaces or tabs until we reach end of line or the delimiter
		if (!err)
		{
			reader.SkipTabsAndSpaces();
		}

		if (!reader.FileFinished() && reader.CurrentCharacter() != delimiter)
		{
			err = true;
		}

		if (err)
		{
			ThrowParseException("in fileread() function: format error at column %u", reader.CharsRead());
		}
	}
}

// Get another operand, called when evaluating a function after we have evaluate the first operand.
// We checked the stack for the call to ParseInternal for the first operand, no need to do it again.
void ExpressionParser::GetNextOperand(ExpressionValue& operand, bool evaluate) THROWS(GCodeException)
{
	if (SkipWhiteSpace() != ',')
	{
		ThrowParseException("expected ','");
	}
	AdvancePointer();
	ParseInternal(operand, evaluate, 0);
}

// Return true if the specified type has no literals and should therefore be converted to string when comparing with another value that is not of the same type.
// We don't need to handle Port and UniqueId types here because we convent them to string before calling this.
/*static*/ bool ExpressionParser::TypeHasNoLiterals(TypeCode t) noexcept
{
	return t == TypeCode::Char || t == TypeCode::DateTime_tc || t == TypeCode::IPAddress_tc || t == TypeCode::MacAddress_tc || t == TypeCode::DriverId_tc
#if SUPPORT_CAN_EXPANSION
		|| t == TypeCode::CanExpansionBoardDetails
#endif
		;
}

// Balance types for a comparison operator
void ExpressionParser::BalanceTypes(ExpressionValue& val1, ExpressionValue& val2, bool evaluate) const THROWS(GCodeException)
{
	// First convert any Uint64 or Uint32 operands to float
	if (val1.GetType() == TypeCode::Uint64 || val1.GetType() == TypeCode::Uint32)
	{
		ConvertToFloat(val1, evaluate);
	}
	if (val2.GetType() == TypeCode::Uint64 || val2.GetType() == TypeCode::Uint32)
	{
		ConvertToFloat(val2, evaluate);
	}

	// Convert any port or unique ID values to string
	if (val1.GetType() == TypeCode::Port || val1.GetType() == TypeCode::UniqueId_tc)
	{
		ConvertToString(val1, evaluate);
	}
	if (val2.GetType() == TypeCode::Port || val2.GetType() == TypeCode::UniqueId_tc)
	{
		ConvertToString(val2, evaluate);
	}

	if ((val1.GetType() == val2.GetType()) || (val1.IsStringType() && val2.IsStringType()))			// handle the common case first
	{
		// nothing to do
	}
	else if (val1.GetType() == TypeCode::Float)
	{
		ConvertToFloat(val2, evaluate);
	}
	else if (val2.GetType() == TypeCode::Float)
	{
		ConvertToFloat(val1, evaluate);
	}
	else if (val2.IsStringType() && TypeHasNoLiterals(val1.GetType()))
	{
		ConvertToString(val1, evaluate);
	}
	else if (val1.IsStringType() && TypeHasNoLiterals(val2.GetType()))
	{
		ConvertToString(val2, evaluate);
	}
	else
	{
		if (evaluate)
		{
			ThrowParseException("cannot convert operands to same type");
		}
		val1.SetInt(0);
		val2.SetInt(0);
	}
}

void ExpressionParser::ConvertToFloat(ExpressionValue& val, bool evaluate) const THROWS(GCodeException)
{
	float fVal;
	switch (val.GetType())
	{
	case TypeCode::Float:
		return;							// no conversion needed, leave the precision alone

	case TypeCode::Uint32:
		fVal = (float)val.uVal;
		break;

	case TypeCode::Uint64:
		fVal = (float)val.Get56BitValue();
		break;

	case TypeCode::Int32:
		fVal = (float)val.iVal;
		break;

	default:
		if (evaluate)
		{
			ThrowParseException("expected numeric operand");
		}
		fVal = 0.0f;
		break;
	}
	val.SetFloat(fVal, 1);
}

void ExpressionParser::ConvertToInteger(ExpressionValue& val, bool evaluate) const THROWS(GCodeException)
{
	switch (val.GetType())
	{
	case TypeCode::Int32:
		break;

	case TypeCode::Uint32:
		if (val.uVal <= (uint32_t)std::numeric_limits<int32_t>::max())
		{
			val.SetInt((int32_t)val.uVal);
			break;
		}
		if (evaluate)
		{
			ThrowParseException("unsigned integer too large");
		}
		val.SetInt(0);
		break;

	default:
		if (evaluate)
		{
			ThrowParseException("expected integer value");
		}
		val.SetInt(0);
		break;
	}
}

void ExpressionParser::ConvertToUnsigned(ExpressionValue& val, bool evaluate) const THROWS(GCodeException)
{
	switch (val.GetType())
	{
	case TypeCode::Uint32:
		break;

	case TypeCode::Int32:
		if (val.iVal >= 0)
		{
			val.SetUnsigned((uint32_t)val.iVal);
			break;
		}
		// no break
	default:
		if (evaluate)
		{
			ThrowParseException(ExpectedNonNegativeIntMessage);
		}
		val.SetUnsigned(0);
	}
}

void ExpressionParser::ConvertToBool(ExpressionValue& val, bool evaluate) const THROWS(GCodeException)
{
	if (val.GetType() != TypeCode::Bool)
	{
		if (evaluate)
		{
			ThrowParseException("expected Boolean operand");
		}
		val.SetBool(false);
	}
}

void ExpressionParser::ConvertToString(ExpressionValue& val, bool evaluate) const noexcept
{
	if (!val.IsStringType())
	{
		if (evaluate)
		{
			String<MaxStringExpressionLength> str;
			val.AppendAsString(str.GetRef());
			StringHandle sh(str.c_str());
			val.SetStringHandle(sh);
		}
		else
		{
			val.SetCString("");
		}
	}
}

void ExpressionParser::ConvertToDriverId(ExpressionValue& val, bool evaluate) const THROWS(GCodeException)
{
	switch (val.GetType())
	{
	case TypeCode::DriverId_tc:
		break;

	case TypeCode::Int32:
#if SUPPORT_CAN_EXPANSION
		val.SetDriverId(DriverId(0, val.uVal));
#else
		val.SetDriverId(DriverId(val.uVal));
#endif
		break;

	case TypeCode::Float:
		{
			const float f10val = 10.0 * val.fVal;
			const int32_t ival = lrintf(f10val);
#if SUPPORT_CAN_EXPANSION
			if (ival >= 0 && fabsf(f10val - (float)ival) <= 0.002)
			{
				val.SetDriverId(DriverId(ival/10, ival % 10));
			}
#else
			if (ival >= 0 && ival < 10 && fabsf(f10val - (float)ival) <= 0.002)
			{
				val.SetDriverId(DriverId(ival % 10));
			}
#endif
			else
			{
				ThrowParseException("invalid driver ID");
			}
		}
		break;

	default:
		if (evaluate)
		{
			ThrowParseException("expected driver ID");
		}
	}
}

void ExpressionParser::ApplyLengthOperator(ExpressionValue& val, bool evaluate) const THROWS(GCodeException)
{
	switch (val.GetType())
	{
	case TypeCode::CString:
		val.SetInt((int32_t)strlen(val.sVal));
		break;

	case TypeCode::HeapString:
		val.SetInt((int32_t)val.shVal.GetLength());
		break;

	case TypeCode::ObjectModelArray:
		{
			const ObjectModelArrayTableEntry *_ecv_null const entry = val.omVal->FindObjectModelArrayEntry(val.param & 0xFF);
			if (entry == nullptr)
			{
				THROW_INTERNAL_ERROR;
			}
			ObjectExplorationContext context;
			context.AddIndex(val.param >> 8);
			ReadLocker lock(entry->lockPointer);
			val.SetInt(entry->GetNumElements(val.omVal, context));
			context.RemoveIndex();
		}
		break;

	case TypeCode::HeapArray:
		{
			ReadLocker lock(Heap::heapLock);				// must have a read lock on heapLock when calling GetNumElements or GetElement
			val.SetInt(val.ahVal.GetNumElements());
		}
		break;

	default:
		if (evaluate)
		{
			ThrowParseException("expected object model value or string after '#");
		}
		val.SetInt(0);
		break;
	}
}

void ExpressionParser::CheckForExtraCharacters() THROWS(GCodeException)
{
	if (SkipWhiteSpace() != 0)
	{
		ThrowParseException("Unexpected characters after expression");
	}
}

// Parse a number. The initial character of the string is a decimal digit.
void ExpressionParser::ParseNumber(ExpressionValue& rslt) noexcept
{
	NumericConverter conv;
	conv.Accumulate(CurrentCharacter(), NumericConverter::AcceptSignedFloat | NumericConverter::AcceptHex, [this]()->char { AdvancePointer(); return CurrentCharacter(); });	// must succeed because CurrentCharacter is a decimal digit

	if (conv.FitsInInt32())
	{
		rslt.SetInt(conv.GetInt32());
	}
	else if (conv.FitsInUint32())
	{
		rslt.SetUnsigned(conv.GetUint32());
	}
	else
	{
		rslt.SetFloat(conv.GetFloat(), constrain<unsigned int>(conv.GetDigitsAfterPoint(), 1, MaxFloatDigitsDisplayedAfterPoint));
	}
}

static void SetStrStrResult(ExpressionValue& e, const char *_ecv_array s1, const char *_ecv_array s2) noexcept
{
	const char *_ecv_array _ecv_null q = strstr(s1, s2);
	e.SetInt((q == nullptr) ? -1 : q - s1);
}

void ExpressionParser::SetFindResult(ExpressionValue& e1, const char *_ecv_array s, const ExpressionValue& e2) THROWS(GCodeException)
{
	switch (e2.GetType())
	{
	case TypeCode::Char:
		{
			const char *_ecv_array _ecv_null q = strchr(s, e2.cVal);
			e1.SetInt((q == nullptr) ? -1 : q - s);
		}
		break;

	case TypeCode::CString:
		SetStrStrResult(e1, s, e2.sVal);
		break;

	case TypeCode::HeapString:
		{
			ReadLockedPointer<const char> p = e2.shVal.Get();
			SetStrStrResult(e1, s, p.Ptr());
		}
		break;

	default:
		ThrowParseException("incompatible operand types");
	}
}

// Parse an identifier expression
// If 'evaluate' is false then the object model path may not exist, in which case we must ignore error that and parse it all anyway
// This means we can use expressions such as: if {a.b == null || a.b.c == 1}
// *** This function is recursive, so keep its stack usage low!
void ExpressionParser::ParseIdentifierExpression(ExpressionValue& rslt, bool evaluate, bool applyLengthOperator, bool applyExists) THROWS(GCodeException)
{
	char c = CurrentCharacter();
	if (!isAlpha(c))
	{
		ThrowParseException("expected an identifier");
	}

	String<MaxVariableNameLength> id;
	ObjectExplorationContext context(gb, applyLengthOperator, applyExists, (gb != nullptr) ? gb->GetLineNumber() : 0, GetColumn());

	// Loop parsing identifiers and index expressions
	// When we come across an index expression, evaluate it, add it to the context, and place a marker in the identifier string.
	bool isIdentifierCharacter = true;
	do
	{
		AdvancePointer();
		if (c == '[')
		{
			ExpressionValue index;
			CheckStack(StackUsage::ParseInternal);
			ParseInternal(index, evaluate, 0);
			if (CurrentCharacter() != ']')
			{
				ThrowParseException("expected ']'");
			}
			if (index.GetType() != TypeCode::Int32)
			{
				if (evaluate)
				{
					ThrowParseException("expected integer expression");
				}
				index.SetInt(0);
			}
			AdvancePointer();										// skip the ']'
			context.ProvideIndex(index.iVal);
			c = '^';												// add the marker
		}

		if (id.cat(c))
		{
			ThrowParseException("variable name too long");
		}

		// Get the next character, skipping white space that is not inside an identifier
		bool hadIdentifierSpace = false;
		for (;;)
		{
			c = CurrentCharacter();
			if (c != ' ' && c != '\t')
			{
				break;
			}
			hadIdentifierSpace = isIdentifierCharacter;
			AdvancePointer();
		}
		isIdentifierCharacter = (isAlnum(c) || c == '_');
		if (isIdentifierCharacter && hadIdentifierSpace)
		{
			break;													// don't allow spaces inside identifiers
		}
	} while (isIdentifierCharacter || c == '.' || c == '[');

	// Check for the names of constants
	NamedConstant whichConstant(id.c_str());
	if (whichConstant.IsValid())
	{
		if (context.WantExists())
		{
			ThrowParseException(InvalidExistsMessage);
		}

		switch (whichConstant.RawValue())
		{
		case NamedConstant::_true:
			rslt.SetBool(true);
			return;

		case NamedConstant::_false:
			rslt.SetBool(false);
			return;

		case NamedConstant::_null:
			rslt.SetNull(nullptr);
			return;

		case NamedConstant::pi:
			rslt.SetFloat(Pi);
			return;

		case NamedConstant::iterations:
			{
				int32_t v;
				if (gb == nullptr || (v = gb->CurrentFileMachineState().GetIterations()) < 0)
				{
					ThrowParseException("'iterations' used when not inside a loop");
				}
				rslt.SetInt(v);
			}
			return;

		case NamedConstant::_result:
			{
				if (gb == nullptr)
				{
					rslt.SetNull(nullptr);
				}
				else
				{
					int32_t res;
					switch (gb->GetLastResult())
					{
					case GCodeResult::ok:
						res = 0;
						break;

					case GCodeResult::warning:
					case GCodeResult::warningNotSupported:
						res = 1;
						break;

					case GCodeResult::m291Cancelled:
						res = -1;
						break;

					default:
						res = 2;
						break;
					}
					rslt.SetInt(res);
				}
			}
			return;

		case NamedConstant::line:
			rslt.SetInt(gb != nullptr ? (int32_t)gb->GetLineNumber() : 0);
			return;

		case NamedConstant::input:
			if (gb != nullptr)
			{
				rslt = gb->GetM291Result();
			}
			else
			{
				rslt.SetNull(nullptr);
			}
			return;

		default:
			THROW_INTERNAL_ERROR;
		}
	}

	// Check whether it is a function call
	if (SkipWhiteSpace() == '(')
	{
		// It's a function call
		if (context.WantExists())
		{
			ThrowParseException(InvalidExistsMessage);
		}

		const Function func(id.c_str());
		if (!func.IsValid())
		{
			ThrowParseException("unknown function");
		}

		AdvancePointer();
		if (func == Function::exists)
		{
			CheckStack(StackUsage::ParseIdentifierExpression);
			ParseIdentifierExpression(rslt, evaluate, false, true);
		}
		else
		{
			CheckStack(StackUsage::ParseInternal);
			ParseInternal(rslt, evaluate, 0);					// evaluate the first operand

			switch (func.RawValue())
			{
			case Function::abs:
				switch (rslt.GetType())
				{
				case TypeCode::Int32:
					rslt.iVal = labs(rslt.iVal);
					break;

				case TypeCode::Float:
					rslt.fVal = fabsf(rslt.fVal);
					break;

				default:
					if (evaluate)
					{
						ThrowParseException("expected numeric operand");
					}
					rslt.SetInt(0);
				}
				break;

			case Function::sin:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = sinf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::cos:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = cosf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::tan:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = tanf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::asin:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = asinf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::acos:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = acosf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::atan:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = atanf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::atan2:
				{
					ConvertToFloat(rslt, evaluate);
					ExpressionValue nextOperand;
					GetNextOperand(nextOperand, evaluate);
					ConvertToFloat(nextOperand, evaluate);
					rslt.fVal = atan2f(rslt.fVal, nextOperand.fVal);
					rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				}
				break;

			case Function::degrees:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = rslt.fVal * RadiansToDegrees;
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::radians:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = rslt.fVal * DegreesToRadians;
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::sqrt:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = fastSqrtf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::square:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = fsquare(rslt.fVal);
				break;

			case Function::isnan:
				ConvertToFloat(rslt, evaluate);
				rslt.SetBool(std::isnan(rslt.fVal));
				break;

			case Function::floor:
			case Function::ceil:
			case Function::round:
				{
					ConvertToFloat(rslt, evaluate);
					const float f = ((func.RawValue() == Function::floor) ? floorf : (func.RawValue() == Function::ceil) ? ceilf : rintf)(rslt.fVal);
					if (f <= (float)std::numeric_limits<int32_t>::max() && f >= (float)std::numeric_limits<int32_t>::min())
					{
						rslt.SetInt((int32_t)f);
					}
					else
					{
						rslt.fVal = f;
					}
				}
				break;

			case Function::mod:
				{
					ExpressionValue nextOperand;
					GetNextOperand(nextOperand, evaluate);
					BalanceNumericTypes(rslt, nextOperand, evaluate);
					if (rslt.GetType() == TypeCode::Float)
					{
						rslt.fVal = fmod(rslt.fVal, nextOperand.fVal);
					}
					else if (nextOperand.iVal == 0)
					{
						rslt.iVal = 0;
					}
					else
					{
						rslt.iVal %= nextOperand.iVal;
					}
				}
				break;

			case Function::max:
			case Function::min:
				if (SkipWhiteSpace() != ',')
				{
					// Only one operand, so it's min or max on an array
					if (rslt.GetType() != TypeCode::HeapArray)
					{
						ThrowParseException("operand is not an array");
					}

					ReadLocker lock(Heap::heapLock);				// must have a read lock on heapLock when calling GetNumElements or GetElement
					ExpressionValue rVal;
					if (!rslt.ahVal.GetElement(0, rVal))
					{
						ThrowParseException("array has no elements");
					}

					for (size_t i = 1; ; ++i)
					{
						ExpressionValue nextVal;
						if (!rslt.ahVal.GetElement(i, nextVal))
						{
							break;									// quit when index out of bounds
						}
						EvaluateMinOrMax(rVal, nextVal, evaluate, func.RawValue() == Function::max);
					}
					rslt = rVal;
				}
				else
				{
					// We have a command after the first operand, so it's a multi-operand min or max
					do
					{
						AdvancePointer();			// skip the comma
						ExpressionValue nextOperand;
						// We recently checked the stack for a call to ParseInternal, no need to do it again
						ParseInternal(nextOperand, evaluate, 0);
						EvaluateMinOrMax(rslt, nextOperand, evaluate, func.RawValue() == Function::max);
					} while (SkipWhiteSpace() == ',');
				}
				break;

			case Function::random:
				{
					uint32_t limit;
					if (rslt.GetType() == TypeCode::Uint32)
					{
						limit = rslt.uVal;
					}
					else if (rslt.GetType() == TypeCode::Int32 && rslt.iVal > 0)
					{
						limit = rslt.iVal;
					}
					else
					{
						ThrowParseException("expected positive integer");
					}
					rslt.SetInt((int32_t)random(limit));
				}
				break;

			case Function::datetime:
				{
					uint64_t val;
					switch (rslt.GetType())
					{
					case TypeCode::Int32:
						val = (uint64_t)max<uint32_t>(rslt.iVal, 0);
						break;

					case TypeCode::Uint32:
						val = (uint64_t)rslt.uVal;
						break;

					case TypeCode::Uint64:
					case TypeCode::DateTime_tc:
						val = rslt.Get56BitValue();
						break;

					case TypeCode::CString:
						val = ParseDateTime(rslt.sVal);
						break;

					case TypeCode::HeapString:
						val = ParseDateTime(rslt.shVal.Get().Ptr());
						break;

					default:
						ThrowParseException("can't convert value to DateTime");
					}
					rslt.SetDateTime(val);
				}
				break;

			case Function::fileexists:
				ConvertToString(rslt, evaluate);
				{
					bool b;
					switch (rslt.GetType())
					{
					case TypeCode::CString:
						b = reprap.GetPlatform().SysFileExists(rslt.sVal);
						break;

					case TypeCode::HeapString:
						{
							ReadLockedPointer<const char> p = rslt.shVal.Get();
							// We assume that calling SysFileExists doesn't need to use the heap, so we are OK keeping the lock around the call and we don't need to copy the string
							b = reprap.GetPlatform().SysFileExists(p.Ptr());
						}
						break;

					default:
						b = false;
						break;
					}
					rslt.SetBool(b);
				}
				break;

			case Function::fileread:
				{
					if (evaluate && rslt.GetType() != TypeCode::CString && rslt.GetType() != TypeCode::HeapString)
					{
						ThrowParseException("expected string operand");
					}

					ExpressionValue integerOperand;
					GetNextOperand(integerOperand, evaluate);
					if (evaluate && (integerOperand.GetType() != TypeCode::Int32 || integerOperand.iVal < 0))
					{
						ThrowParseException(ExpectedNonNegativeIntMessage);
					}
					const unsigned int offset = (unsigned int)integerOperand.iVal;

					GetNextOperand(integerOperand, evaluate);
					if (evaluate && (integerOperand.GetType() != TypeCode::Int32 || integerOperand.iVal < 0))
					{
						ThrowParseException(ExpectedNonNegativeIntMessage);
					}

					ExpressionValue delimiterOperand;
					GetNextOperand(delimiterOperand, evaluate);
					if (evaluate && delimiterOperand.GetType() != TypeCode::Char)
					{
						ThrowParseException("expected character operand");
					}

					CheckStack(StackUsage::ReadArrayFromFile);
					ReadArrayFromFile(rslt, offset, (unsigned int)integerOperand.iVal, delimiterOperand.cVal);
				}
				break;

			case Function::vector:		// vector(numElements, elementValue)
				if (evaluate && (rslt.GetType() != TypeCode::Int32 || rslt.iVal < 0))
				{
					ThrowParseException(ExpectedNonNegativeIntMessage);
				}
				{
					ExpressionValue valueOperand;
					GetNextOperand(valueOperand, evaluate);
					if (evaluate)
					{
						const size_t numElems = (size_t)rslt.iVal;
						ArrayHandle ah;
						{
							WriteLocker locker(Heap::heapLock);						// prevent other tasks modifying the heap
							ah.Allocate(numElems);
							for (size_t i = 0; i < numElems; ++i)
							{
								ah.AssignElement(i, valueOperand);
							}
						}
						rslt.SetArrayHandle(ah);
					}
					else
					{
						rslt.SetNull(nullptr);
					}
					break;
				}
				break;

			case Function::exp:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = expf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::log:
				ConvertToFloat(rslt, evaluate);
				rslt.fVal = logf(rslt.fVal);
				rslt.param = MaxFloatDigitsDisplayedAfterPoint;
				break;

			case Function::pow:
				{
					ExpressionValue nextOperand;
					GetNextOperand(nextOperand, evaluate);
					BalanceNumericTypes(rslt, nextOperand, evaluate);

					// If both operands are integer and the second one is non-negative, result is integer if it fits
					const bool integerResult = (nextOperand.GetType() == TypeCode::Int32) && nextOperand.iVal >= 0;

					ConvertToFloat(rslt, evaluate);
					ConvertToFloat(nextOperand, evaluate);
					const float fres = powf(rslt.fVal, nextOperand.fVal);
					if (integerResult && fabsf(fres) <= (float)std::numeric_limits<int32_t>::max())
					{
						rslt.SetInt(lrintf(fres));
					}
					else
					{
						rslt.fVal = fres;
						rslt.param = MaxFloatDigitsDisplayedAfterPoint;
					}
				}
				break;

			case Function::take:
				{
					ExpressionValue nextOperand;
					GetNextOperand(nextOperand, evaluate);
					ConvertToUnsigned(nextOperand, evaluate);
					switch (rslt.GetType())
					{
					case TypeCode::ObjectModelArray:
						{
							const ObjectModelArrayTableEntry *const entry = _ecv_not_null(rslt.omVal->FindObjectModelArrayEntry(rslt.param & 0xFF));
							ObjectExplorationContext context;
							ReadLocker locker(entry->lockPointer);
							const size_t len = min<size_t>(entry->GetNumElements(rslt.omVal, context), nextOperand.uVal);
							ArrayHandle ah;
							WriteLocker lock(Heap::heapLock);
							ah.Allocate(len);
							for (size_t i = 0; i < len; ++i)
							{
								context.AddIndex(i);
								ExpressionValue elem(entry->GetElement(rslt.omVal, context));
								ah.AssignElement(i, elem);
								context.RemoveIndex();
							}
							rslt.SetArrayHandle(ah);
						}
						break;

					case TypeCode::HeapArray:
						{
							WriteLocker lock(Heap::heapLock);
							const size_t len = min<size_t>(rslt.ahVal.GetNumElements(), nextOperand.uVal);
							ArrayHandle ah;
							ah.Allocate(len);
							for (size_t i = 0; i < len; ++i)
							{
								ExpressionValue elem;
								(void)rslt.ahVal.GetElement(i, elem);
								ah.AssignElement(i, elem);
							}
							rslt.SetArrayHandle(ah);
						}
						break;

					case TypeCode::CString:
						{
							const size_t len = min<size_t>(strlen(rslt.sVal), nextOperand.uVal);
							const StringHandle sh(rslt.sVal, len);
							rslt.SetStringHandle(sh);
						}
						break;

					case TypeCode::HeapString:
						{
							ExpressionValue copy(rslt);
							WriteLocker lock(Heap::heapLock);
							const ReadLockedPointer<const char> p = copy.shVal.Get();
							const size_t len = min<size_t>(strlen(p.Ptr()), nextOperand.uVal);
							const StringHandle sh(p.Ptr(), len);
							rslt.SetStringHandle(sh);
						}
						break;

					default:
						if (evaluate) { ThrowParseException("first operand of function is not an array or string"); }
						rslt.SetNull(nullptr);
						break;
					}
				}
				break;

			case Function::drop:
				{
					ExpressionValue nextOperand;
					GetNextOperand(nextOperand, evaluate);
					ConvertToUnsigned(nextOperand, evaluate);
					switch (rslt.GetType())
					{
					case TypeCode::ObjectModelArray:
						{
							const ObjectModelArrayTableEntry *const entry = _ecv_not_null(rslt.omVal->FindObjectModelArrayEntry(rslt.param & 0xFF));
							ObjectExplorationContext context;
							ReadLocker locker(entry->lockPointer);
							const size_t numOriginalElements = entry->GetNumElements(rslt.omVal, context);
							const size_t offset = min<size_t>(numOriginalElements, nextOperand.uVal);
							const size_t len = numOriginalElements - offset;
							ArrayHandle ah;
							if (len != 0)
							{
								WriteLocker lock(Heap::heapLock);
								ah.Allocate(len);
								ObjectExplorationContext context;
								for (size_t i = 0; i < len; ++i)
								{
									context.AddIndex(i + offset);
									ExpressionValue elem(entry->GetElement(rslt.omVal, context));
									ah.AssignElement(i, elem);
									context.RemoveIndex();
								}
							}
							rslt.SetArrayHandle(ah);
						}
						break;

						case TypeCode::HeapArray:
						{
							WriteLocker lock(Heap::heapLock);
							const size_t numOriginalElements = rslt.ahVal.GetNumElements();
							const size_t offset = min<size_t>(numOriginalElements, nextOperand.uVal);
							const size_t len = numOriginalElements - offset;
							ArrayHandle ah;
							if (len != 0)
							{
								ah.Allocate(len);
								for (size_t i = 0; i < len; ++i)
								{
									ExpressionValue elem;
									(void)rslt.ahVal.GetElement(i + offset, elem);
									ah.AssignElement(i, elem);
								}
							}
							rslt.SetArrayHandle(ah);
						}
						break;

					case TypeCode::CString:
						{
							const size_t slen = strlen(rslt.sVal);
							const size_t offset = min<size_t>(slen, nextOperand.uVal);
							StringHandle sh(rslt.sVal + offset, slen - offset);
							rslt.SetStringHandle(sh);
						}
						break;

					case TypeCode::HeapString:
						{
							ExpressionValue copy(rslt);
							WriteLocker lock(Heap::heapLock);
							const ReadLockedPointer<const char> p = copy.shVal.Get();
							const size_t slen = strlen(p.Ptr());
							const size_t offset = min<size_t>(slen, nextOperand.uVal);
							StringHandle sh(p.Ptr() + offset, slen - offset);
							rslt.SetStringHandle(sh);
						}
						break;

					default:
						if (evaluate) { ThrowParseException("first operand of function is not an array or string"); }
						rslt.SetNull(nullptr);
						break;
					}
				}
				break;

			case Function::find:
				{
					ExpressionValue nextOperand;
					GetNextOperand(nextOperand, evaluate);
					switch (rslt.GetType())
					{
					case TypeCode::CString:
						SetFindResult(rslt, rslt.sVal, nextOperand);
						break;

					case TypeCode::HeapString:
						{
							ReadLockedPointer<const char> p1 = rslt.shVal.Get();
							SetFindResult(rslt, p1.Ptr(), nextOperand);
						}
						break;

					// find() on arrays is not yet implemented but may be in future
					case TypeCode::ObjectModelArray:
					case TypeCode::HeapArray:
					default:
						if (evaluate) { ThrowParseException("first operand of function is not a string"); }
						rslt.SetNull(nullptr);
						break;
					}
				}
				break;

			default:
				THROW_INTERNAL_ERROR;
			}
		}

		if (SkipWhiteSpace() != ')')
		{
			ThrowParseException("expected ')'");
		}
		AdvancePointer();
		return;
	}

	// If we are not evaluating then the object expression doesn't have to exist, so don't retrieve it because that might throw an error
	if (evaluate)
	{
		// Check for a parameter, local or global variable
		if (StringStartsWith(id.c_str(), "param."))
		{
			if (gb == nullptr)
			{
				rslt.SetNull(nullptr);
			}
			else
			{
				GetVariableValue(rslt, &gb->GetVariables(), id.c_str() + strlen("param."), context, true, applyLengthOperator, applyExists);
			}
			return;
		}

		if (StringStartsWith(id.c_str(), "global."))
		{
			auto vars = reprap.GetGlobalVariablesForReading();
			GetVariableValue(rslt, vars.Ptr(), id.c_str() + strlen("global."), context, false, applyLengthOperator, applyExists);
			return;
		}

		if (StringStartsWith(id.c_str(), "var."))
		{
			if (gb == nullptr)
			{
				rslt.SetNull(nullptr);
			}
			else
			{
				GetVariableValue(rslt, &gb->GetVariables(), id.c_str() + strlen("var."), context, false, applyLengthOperator, applyExists);
			}
			return;
		}

		if (StringStartsWith(id.c_str(), "job.file.customInfo."))
		{
			auto vars = reprap.GetPrintMonitor().GetCustomInfoForReading();
			GetVariableValue(rslt, vars.Ptr(), id.c_str() + strlen("job.file.customInfo."), context, false, applyLengthOperator, applyExists);
			return;
		}

		// "exists(var)", "exists(param)" and "exists(global)" should return true.
		// "exists(global)" will anyway because "global" is a root key in the object model. Handle the other two here.
		if (applyExists && (strcmp(id.c_str(), "param") == 0 || strcmp(id.c_str(), "var") == 0))
		{
			rslt.SetBool(true);
			return;
		}

		// Else assume an object model value
		CheckStack(StackUsage::GetObjectValueUsingTableNumber);
		rslt = reprap.GetObjectValueUsingTableNumber(context, nullptr, id.c_str(), 0);
		if (context.ObsoleteFieldQueried() && obsoleteField.IsEmpty())
		{
			obsoleteField.copy(id.c_str());
		}
		return;
	}
	rslt.SetNull(nullptr);
}

// Parse a string to a DateTime
time_t ExpressionParser::ParseDateTime(const char *_ecv_array s) const THROWS(GCodeException)
{
	tm timeInfo;
	if (SafeStrptime(s, "%Y-%m-%dT%H:%M:%S", &timeInfo) == nullptr)
	{
		ThrowParseException("string is not a valid date and time");
	}
	return mktime(&timeInfo);
}

// Get the value of a variable or part of a variable. We have already checked that 'evaluate' is true before calling this.
void ExpressionParser::GetVariableValue(ExpressionValue& rslt, const VariableSet *vars, const char *_ecv_array name, ObjectExplorationContext& context, bool isParameter, bool applyLengthOperator, bool wantExists) THROWS(GCodeException)
{
	const char *_ecv_array _ecv_null pos = strchr(name, '^');
	if (pos != nullptr)
	{
		// Indexing into a variable
		const Variable *_ecv_null const var = vars->Lookup(name, pos - name, isParameter);
		if (var != nullptr)
		{
			ExpressionValue val = var->GetValue();
			while (val.GetType() == TypeCode::HeapArray)
			{
				ExpressionValue elem;
				{
					context.AddIndex();								// we are about to use up an index
					const int32_t index = context.GetLastIndex();
					ReadLocker lock(Heap::heapLock);				// must have a read lock on heapLock when calling GetNumElements or GetElement
					if (!val.ahVal.GetElement(index, elem))
					{
						if (context.WantExists())
						{
							rslt.SetBool(false);
							return;
						}
						ThrowParseException(ArrayIndexOutOfRangeText);
					}
				}

				++pos;												// skip the '^'
				if (*pos == 0)
				{
					// End of the expression
					if (wantExists)
					{
						rslt.SetBool(true);
					}
					else
					{
						rslt = elem;
						if (applyLengthOperator)
						{
							ApplyLengthOperator(rslt, true);
						}
					}
					return;
				}
				if (*pos == '^')
				{
					val = elem;
					continue;
				}
				if (*pos == '.' && elem.GetType() == TypeCode::ObjectModel_tc)
				{
					rslt = elem.omVal->GetObjectValueUsingTableNumber(context, nullptr, pos + 1, 0);
					return;
				}
				ThrowParseException("Error indexing into nested arrays");
			}
			ThrowParseException("Cannot index into variable or parameter '%s' of non-array type", name);
		}
		else if (wantExists)
		{
			rslt.SetBool(false);
			return;
		}
		// else fall through to throw error
	}
	else
	{
		const Variable *_ecv_null const var = vars->Lookup(name, strlen(name), isParameter);
		if (wantExists)
		{
			rslt.SetBool(var != nullptr);
			return;
		}

		if (var != nullptr)
		{
			rslt = var->GetValue();
			if (applyLengthOperator)
			{
				ApplyLengthOperator(rslt, true);
			}
			return;
		}
		// else fall through to throw error
	}

	ThrowParseException((isParameter) ? "unknown parameter '%s'" : "unknown variable '%s'", name);
}

// Parse a quoted string, given that the current character is double-quote
// This is almost a copy of InternalGetQuotedString in class StringParser
void ExpressionParser::ParseQuotedString(ExpressionValue& rslt) THROWS(GCodeException)
{
	String<MaxStringExpressionLength> str;
	AdvancePointer();
	while (true)
	{
		char c = CurrentCharacter();
		AdvancePointer();
		if (c < ' ')
		{
			ThrowParseException("control character in string");
		}
		if (c == '"')
		{
			if (CurrentCharacter() != c)
			{
				StringHandle sh(str.c_str());
				rslt.SetStringHandle(sh);
				return;
			}
			AdvancePointer();
		}
		else if (c == '\'')
		{
			if (isAlpha(CurrentCharacter()))
			{
				// Single quote before an alphabetic character forces that character to lower case
				c = (char)tolower(CurrentCharacter());
				AdvancePointer();
			}
			else if (CurrentCharacter() == c)
			{
				// Two quotes are used to represent one
				AdvancePointer();
			}
		}
		if (str.cat(c))
		{
			ThrowParseException("string too long");
		}
	}
}

void ExpressionParser::ParseCharacter(ExpressionValue& rslt) THROWS(GCodeException)
{
	AdvancePointer();
	rslt.SetChar(CurrentCharacter());
	AdvancePointer();
	if (CurrentCharacter() != '\'')
	{
		ThrowParseException("expected \"'\"");
	}
	AdvancePointer();
}

// Return the current character, or 0 if we have run out of string
char ExpressionParser::CurrentCharacter() const noexcept
{
	return (currentp < endp) ? *currentp : 0;
}

// Skip any whitespace and return the next character, or 0 if we have run out of string
char ExpressionParser::SkipWhiteSpace() noexcept
{
	char c;
	while ((c = CurrentCharacter()) == ' ' || c == '\t')
	{
		++currentp;
	}
	return c;
}

void ExpressionParser::AdvancePointer() noexcept
{
	if (currentp < endp)
	{
		++currentp;
	}
}

int ExpressionParser::GetColumn() const noexcept
{
	return (column < 0) ? column : (currentp - startp) + column;
}

void ExpressionParser::ThrowParseException(const char *_ecv_array str) const THROWS(GCodeException)
{
	throw GCodeException(gb, GetColumn(), str);
}

void ExpressionParser::ThrowParseException(const char *_ecv_array str, const char *_ecv_array param) const THROWS(GCodeException)
{
	throw GCodeException(gb, GetColumn(), str, param);
}

void ExpressionParser::ThrowParseException(const char *_ecv_array str, uint32_t param) const THROWS(GCodeException)
{
	throw GCodeException(gb, GetColumn(), str, param);
}

// Call this before making a recursive call, or before calling a function that needs a lot of stack from a recursive function
void ExpressionParser::CheckStack(uint32_t calledFunctionStackUsage) const THROWS(GCodeException)
{
	const char *_ecv_array stackPtr = (const char *_ecv_array)GetStackPointer();
	const char *_ecv_array stackLimit = (const char *_ecv_array)TaskBase::GetCurrentTaskStackBase();	// get the base (lowest available address) of the stack for this task

	//debugPrintf("Margin: %u\n", stackPtr - stackLimit);
	if (stackLimit + calledFunctionStackUsage + (StackUsage::Throw + StackUsage::Margin) <= stackPtr)
	{
		return;				// we have enough stack
	}

	// The stack is in danger of overflowing. Throw an exception if we have enough stack to do so (ideally, this should always be the case)
	if (stackLimit + StackUsage::Throw <= stackPtr)
	{
		throw GCodeException(gb, GetColumn(), "Expression nesting too deep");
	}

	// Not enough stack left to throw an exception
	SoftwareReset(SoftwareResetReason::stackOverflow, (const uint32_t *_ecv_array)stackPtr);
}

// End
